package com.koron.css2.newProjectCenter.impl.pubUpTime;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import com.koron.css2.newProjectCenter.bean.PubUpTimeCustom;
import com.koron.css2.newProjectCenter.bean.WorkDayBean;
import com.koron.css2.newProjectCenter.bean.query.PubUpTimeCustomQuery;
import com.koron.css2.newProjectCenter.bean.query.PubUpTimeInstanceQuery;
import com.koron.css2.newProjectCenter.mapper.PubUpTimeCustomMapper;
import com.koron.css2.newProjectCenter.mapper.PubUpTimeInstanceMapper;
import com.koron.css2.newProjectCenter.mapper.WorkDayMapper;
import com.koron.css2.serviceManage.bean.UserInfoBean;
import com.koron.util.Tools;
import org.koron.ebs.mybatis.SessionFactory;

import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;

public class WorkDayUtils {
    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");

    /**
     * 保存用户信息到特定节点的发布实例
     * <br/>
     * 此方法主要用于在给定的流程实例ID和目标节点下，保存用户信息到发布实例中它首先查询与目标节点关联的发布配置，
     * 然后创建一个新的发布实例，填充用户信息和时间相关字段，最后保存这个实例到数据库中
     *
     * @param factory           Session工厂，用于获取数据库映射器
     * @param userInfo          用户信息，包括用户账号、名称等
     * @param toNodeCode        目标节点代码，表示用户信息应保存到的流程节点
     * @param processInstanceId 流程实例ID，标识特定的流程实例
     */
    public static void save(SessionFactory factory, UserInfoBean userInfo, String toNodeCode, String processInstanceId) {

        PubUpTimeCustomMapper customMapper = factory.getMapper(PubUpTimeCustomMapper.class);

        PubUpTimeCustomQuery bean = new PubUpTimeCustomQuery();
        bean.setNode(toNodeCode);

        List<PubUpTimeCustom> pubUpTimeCustom = customMapper.selecList(bean);

        if (pubUpTimeCustom != null && !pubUpTimeCustom.isEmpty()) {

            PubUpTimeInstanceQuery pubUpTimeInstance = new PubUpTimeInstanceQuery();

            BeanUtil.copyProperties(pubUpTimeCustom.get(0), pubUpTimeInstance);

            PubUpTimeInstanceMapper pubUpTimeInstanceMapper = factory.getMapper(PubUpTimeInstanceMapper.class);
            pubUpTimeInstance.setId(Tools.getObjectId());
            pubUpTimeInstance.setStatus(0);
            pubUpTimeInstance.setUpdateTime(new DateTime());
            pubUpTimeInstance.setFlowId(processInstanceId);
            pubUpTimeInstance.setUpdateAccount(userInfo.getUserInfo().getAcount());
            pubUpTimeInstance.setUpdateName(userInfo.getUserInfo().getName());
            pubUpTimeInstance.setCreateAccount(userInfo.getUserInfo().getAcount());
            pubUpTimeInstance.setCreateTime(new DateTime());
            pubUpTimeInstance.setCreateName(userInfo.getUserInfo().getName());
            pubUpTimeInstance.setTenantId(userInfo.getCurWaterCode());
            pubUpTimeInstance.setStartTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new DateTime()));

            pubUpTimeInstance.setEndTime(calcEndTime(factory.getMapper(WorkDayMapper.class), LocalDateTime.now(), pubUpTimeCustom.get(0).getTimeNum()));
            pubUpTimeInstanceMapper.updateOrSave(pubUpTimeInstance);
            if (pubUpTimeInstance.getSortNo() != null && pubUpTimeInstance.getSortNo() > 2) {
                pubUpTimeInstance.setSortNo(pubUpTimeInstance.getSortNo() - 1);
                pubUpTimeInstanceMapper.flagFinished(pubUpTimeInstance);
            }
        }
    }

    public static String calcEndTime(WorkDayMapper mapper, LocalDateTime startTime, double estimatedHours) {
        double remainingHours = estimatedHours;

        LocalDateTime currentTime = startTime;

        while (remainingHours > 0) {

            WorkDayBean bean = mapper.selectSpecialDate(currentTime.toLocalDate().toString());
            if (bean == null) {
                return null;
            }

            if (bean.getStatus() == 1 || bean.getStatus() == 3) {
                currentTime = currentTime.plusDays(1).withHour(bean.getMorningStart().getHours()).withMinute(0).withSecond(0);
                continue;
            }

            double availableHoursToday = restMinutesOfTheDay(bean, currentTime) / 60.0;

            if (availableHoursToday >= remainingHours) {

                currentTime = addHoursToSchedule(bean, currentTime, remainingHours);
                remainingHours = 0;
            } else {

                remainingHours -= availableHoursToday;
                currentTime = currentTime.plusDays(1).withHour(bean.getMorningStart().getHours()).withMinute(0).withSecond(0);
            }
        }
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        return currentTime.format(formatter);
    }

    public static Date convertStringToDate(String dateTimeString) {
        if (dateTimeString == null || dateTimeString.isEmpty())
            return null;
        // 定义日期时间格式
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

        // 将字符串解析为LocalDateTime对象
        LocalDateTime localDateTime = LocalDateTime.parse(dateTimeString, formatter);

        // 将LocalDateTime对象转换为Date对象
        return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
    }

    private static long restMinutesOfTheDay(WorkDayBean schedule, LocalDateTime currentTime) {
        long totalMinutes = 0;

        if (isBeforeMorningStart(currentTime, schedule.getMorningStart())) {
            LocalDateTime morningStart = LocalDateTime.ofInstant(schedule.getMorningStart().toInstant(), ZoneId.systemDefault());
            currentTime = LocalDateTime.of(currentTime.toLocalDate(), morningStart.toLocalTime());
        }

        if (isBetween(currentTime, schedule.getMorningEnd(), schedule.getAfternoonStart())) {
            LocalDateTime afternoonStart = LocalDateTime.ofInstant(schedule.getAfternoonStart().toInstant(), ZoneId.systemDefault());
            currentTime = LocalDateTime.of(currentTime.toLocalDate(), afternoonStart.toLocalTime());
        }

        if (isBetween(currentTime, schedule.getMorningStart(), schedule.getMorningEnd())) {
            totalMinutes += minutesBetween(currentTime, schedule.getMorningEnd());

            Date afternoonStart = schedule.getAfternoonStart();

            LocalDateTime afternoonStartDateTime = LocalDateTime.ofInstant(afternoonStart.toInstant(), ZoneId.systemDefault());
            totalMinutes += minutesBetween(afternoonStartDateTime, schedule.getAfternoonEnd());
        }

        if (isBetween(currentTime, schedule.getAfternoonStart(), schedule.getAfternoonEnd())) {
            totalMinutes += minutesBetween(currentTime, schedule.getAfternoonEnd());
        }

        return totalMinutes;
    }

    private static LocalDateTime addHoursToSchedule(WorkDayBean schedule, LocalDateTime currentTime, double hours) {

        if (isBeforeMorningStart(currentTime, schedule.getMorningStart())) {
            LocalDateTime morningStart = dateToLocalDateTime(schedule.getMorningStart());
            currentTime = LocalDateTime.of(currentTime.toLocalDate(), morningStart.toLocalTime());
        }

        if (isBetween(currentTime, schedule.getMorningEnd(), schedule.getAfternoonStart())) {
            LocalDateTime afternoonStart = dateToLocalDateTime(schedule.getAfternoonStart());
            currentTime = LocalDateTime.of(currentTime.toLocalDate(), afternoonStart.toLocalTime());
        }

        if (isBetween(currentTime, schedule.getMorningStart(), schedule.getMorningEnd()) || isBeforeMorningStart(currentTime, schedule.getMorningStart())) {
            double morningHours = hoursBetween(currentTime, schedule.getMorningEnd());
            if (morningHours >= hours) {
                return currentTime.plusHours((long) hours).plusMinutes((long) ((hours % 1) * 60));
            } else {
                hours -= morningHours;
                LocalTime afterStart = dateToLocalDateTime(schedule.getAfternoonStart()).toLocalTime();
                currentTime = LocalDateTime.of(currentTime.toLocalDate(), afterStart);
            }
        }

        if (isBetween(currentTime, schedule.getAfternoonStart(), schedule.getAfternoonEnd())) {
            double afternoonHours = hoursBetween(currentTime, schedule.getAfternoonEnd());
            if (afternoonHours >= hours) {
                return currentTime.plusHours((long) hours).plusMinutes((long) ((hours % 1) * 60));
            }
        }

        return currentTime;
    }

    private static boolean isBeforeMorningStart(LocalDateTime currentTime, Date morningStart) {
        LocalTime time = currentTime.toLocalTime();
        LocalTime startTime = dateToLocalDateTime(morningStart).toLocalTime();
        return time.isBefore(startTime);
    }

    private static boolean isBetween(LocalDateTime currentTime, Date start, Date end) {
        LocalTime time = currentTime.toLocalTime();

        LocalTime startTime = dateToLocalDateTime(start).toLocalTime();
        LocalTime endTime = dateToLocalDateTime(end).toLocalTime();
        return !time.isBefore(startTime) && !time.isAfter(endTime);
    }


    private static double hoursBetween(LocalDateTime start, Date end) {

        LocalTime endTime = dateToLocalDateTime(end).toLocalTime();
        start = start.withSecond(0).withNano(0);
        return Duration.between(start.toLocalTime(), endTime.withSecond(0).withNano(0)).toMinutes() / 60.0;
    }


    public static long minutesBetween(LocalDateTime start, Date end) {

        LocalTime endTime = dateToLocalDateTime(end).toLocalTime();
        start = start.withSecond(0).withNano(0);
        return Duration.between(start.toLocalTime(), endTime.withSecond(0).withNano(0)).toMinutes();
    }

    public static long minutesBetweenNow(Date date) {
        // 将Date对象转换为LocalDateTime对象
        LocalDateTime dateTime = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

        // 获取当前时间的LocalDateTime对象
        LocalDateTime now = LocalDateTime.now();

        // 计算两个LocalDateTime对象之间的时间差
        Duration duration = Duration.between(dateTime, now);

        // 将时间差转换为分钟数
        return duration.toMinutes();
    }

    public static long workMinutes(LocalDateTime start, LocalDateTime end, WorkDayMapper mapper) {
        if (start == null || end == null)
            return 0;
        long result = 0;

        if (start.isAfter(end) || start.isEqual(end))
            return result;

        Boolean startRest = isRestDay(start, mapper);
        if (startRest == null)
            return result;
        Boolean endRest = isRestDay(end, mapper);
        if (endRest == null)
            return result;

        if (startRest) {

            WorkDayBean nextDay = mapper.selectSpecialDate(start.plusDays(1).toLocalDate().toString());
            if (nextDay == null)
                return result;
            while (nextDay.getStatus() == 1 || nextDay.getStatus() == 3) {

                LocalDate date = dateToLocalDateTime(nextDay.getDate()).toLocalDate();
                nextDay = mapper.selectSpecialDate(date.plusDays(1).toString());
            }
            start = LocalDateTime.of(
                    dateToLocalDateTime(nextDay.getDate()).toLocalDate(),
                    dateToLocalDateTime(nextDay.getMorningStart()).toLocalTime()
            );
        }

        if (endRest) {

            WorkDayBean preDay = mapper.selectSpecialDate(end.minusDays(1).toLocalDate().toString());
            if (preDay == null)
                return result;
            while (preDay.getStatus() == 1 || preDay.getStatus() == 3) {

                LocalDate date = preDay.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
                preDay = mapper.selectSpecialDate(date.minusDays(1).toString());
            }
            end = LocalDateTime.of(preDay.getDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate(),
                    preDay.getAfternoonEnd().toInstant().atZone(ZoneId.systemDefault()).toLocalTime());
        }

        if (start.isAfter(end) || start.isEqual(end))
            return result;

        if (start.toLocalDate().equals(end.toLocalDate())) {

            LocalTime startTime = start.toLocalTime();
            LocalTime endTime = end.toLocalTime();

            WorkDayBean schedule = mapper.selectSpecialDate(start.toLocalDate().toString());

            if (startTime.isBefore(dateToLocalDateTime(schedule.getMorningStart()).toLocalTime())) {
                startTime = dateToLocalDateTime(schedule.getMorningStart()).toLocalTime();
            }

            if (endTime.isAfter(dateToLocalDateTime(schedule.getAfternoonEnd()).toLocalTime())) {
                endTime = dateToLocalDateTime(schedule.getAfternoonEnd()).toLocalTime();
            }

            long between = Duration.between(startTime, endTime).toMinutes();

            if (endTime.isBefore(dateToLocalDateTime(schedule.getMorningEnd()).toLocalTime()) || startTime.isAfter(dateToLocalDateTime(schedule.getAfternoonStart()).toLocalTime())) {
                return between;
            }

            long restForMinutes = Duration.between(dateToLocalDateTime(schedule.getMorningEnd()), dateToLocalDateTime(schedule.getAfternoonStart())).toMinutes();
            return between - restForMinutes;
        }

        result += restMinutesOfTheDay(mapper.selectSpecialDate(start.toLocalDate().toString()), start);

        LocalDate date = start.plusDays(1).toLocalDate();
        WorkDayBean bean = mapper.selectSpecialDate(date.toString());

        while (bean.getStatus() == 1 || bean.getStatus() == 3) {
            date = date.plusDays(1);
            bean = mapper.selectSpecialDate(date.toString());
        }

        start = LocalDateTime.of(date, dateToLocalDateTime(bean.getMorningStart()).toLocalTime());

        result += workMinutes(start, end, mapper);
        return result;
    }

    private static Boolean isRestDay(LocalDate localDate, WorkDayMapper mapper) {
        WorkDayBean bean = mapper.selectSpecialDate(localDate.toString());
        if (bean == null)
            return null;
        return bean.getStatus() == 1 || bean.getStatus() == 3;
    }

    public static Boolean isRestDay(LocalDateTime localDateTime, WorkDayMapper mapper) {
        String formattedDate = localDateTime.toLocalDate().format(DATE_FORMATTER);
        return isRestDay(LocalDate.parse(formattedDate), mapper);
    }

    public static Boolean isRestDay(Date date, WorkDayMapper mapper) {
        LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
        return isRestDay(localDate, mapper);
    }

    public static Boolean isRestDay(String dateString, WorkDayMapper mapper) {
        LocalDate localDate = LocalDate.parse(dateString, DATE_FORMATTER);
        return isRestDay(localDate, mapper);
    }

    public static Boolean isRestDay(String dateTimeString, WorkDayMapper mapper, String pattern) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        LocalDate localDate = LocalDate.parse(dateTimeString, formatter);
        return isRestDay(localDate, mapper);
    }

    public static Boolean isRestDay(long timestamp, WorkDayMapper mapper) {
        LocalDate localDate = Instant.ofEpochMilli(timestamp).atZone(ZoneId.systemDefault()).toLocalDate();
        return isRestDay(localDate, mapper);
    }

    private static LocalDateTime dateToLocalDateTime(Date date) {
        return date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
    }
}
